using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.Data;
using Microsoft.Xna.Framework.Input;

namespace GenericExampleParticleSystem
{
    public enum ParticleSystem
    {
        Ball,
        Explosion
    }
    public class GenericPointSpriteParticles : DrawableGameComponent
    {
        protected struct VertexParticle
        {
            public Vector3 Position;
            public Color Color;
            public Vector4 Data;
            
            public VertexParticle(Vector3 position, Color color)
            {
                Position = position;
                Color = color;
                Data = Vector4.Zero;                
            }
            public static readonly VertexElement[] VertexElements = new VertexElement[] {
                new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
                new VertexElement(0, sizeof(float)*3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0),
                new VertexElement(0, sizeof(float)*7, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),                
            };

            public static int SizeInBytes
            {
                get
                {
                    return sizeof(float) * 13;
                }
            }

            public static bool operator !=(VertexParticle left, VertexParticle right)
            {
                return left.GetHashCode() != right.GetHashCode();
            }
            public static bool operator ==(VertexParticle left, VertexParticle right)
            {
                return left.GetHashCode() == right.GetHashCode();
            }
            public override bool Equals(object obj)
            {
                return this == (VertexParticle)obj;
            }
            public override int GetHashCode()
            {
                return Position.GetHashCode() | Color.GetHashCode() | Data.GetHashCode();
            }
            public override string ToString()
            {
                return Position.ToString();
            }
        }
        VertexDeclaration m_vDec;
        //private MarchingCubes mc;
        //private double faceX, faceY, faceZ;
        public int partCount;

        private VertexParticle[] m_sprites;
        private VertexParticle[] m_decimated;
        string textureAsset;
        Texture2D myTexture;

        public int ParticleSize
        {
            get { return m_sprites.Length; }
            set 
            {
                myPointSize = 10;
                RefreshParticles();
            }
        }
        public ParticleSystem ParticleSystem
        {
            get { return system; }
            set { system = value; }
        }
        public Vector3 myPosition;
        public Vector3 myScale;
        public Quaternion myRotation;

        public Color particleColor;
        private bool randomColor;
        public bool RandomColor
        {
            get { return randomColor; }
            set
            {
                randomColor = value;
                RefreshParticles();
            }
        }

        private Random m_rand;
        public float myPointSize = 5f;

        string shaderAsset;
        Effect effect;

        ParticleSystem system;

        ContentManager content;
        GraphicsDeviceManager myDeviceManager;
        GraphicsDevice myDevice;
        private VertexDeclaration positionColorVertexDeclaration;

        public GenericPointSpriteParticles(Game game,GraphicsDeviceManager graphiceDeviceManager, string texture, string shader,int particleCount) : base (game)
        {
            content = new ContentManager(game.Services);
            myDeviceManager = graphiceDeviceManager;
            
            myPosition = Vector3.Zero;
            myScale = Vector3.One;
            myRotation = new Quaternion(0, 0, 0, 1);

            //why rotate ?
            Rotate( Vector3.Left, MathHelper.PiOver2);

            partCount = particleCount;

            system = ParticleSystem.Ball;
            randomColor = true;
            particleColor = Color.YellowGreen;

            textureAsset = texture;
            shaderAsset = shader;
            RefreshParticles();
        }

        public void Rotate(Vector3 axis, float angle)
        {
            axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
            myRotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * myRotation);

        }
        public void Rotate2(Vector3 axis, float angle)
        {
            for (int i=0; i < this.partCount; i++)
            {
            }
        }
        public void Translate(Vector3 distance)
        {
            myPosition += Vector3.Transform(distance, Matrix.CreateFromQuaternion(myRotation));
        }

        public void Revolve(Vector3 target, Vector3 axis, float angle)
        {
            Rotate(axis, angle);
            Vector3 revolveAxis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
            Quaternion rotate = Quaternion.CreateFromAxisAngle(revolveAxis, angle);
            myPosition = Vector3.Transform(target - myPosition, Matrix.CreateFromQuaternion(rotate));
        }

        protected override void UnloadContent()
        {
            content.Unload();
            base.UnloadContent();
        }

        protected override void LoadContent()
        {
                myDevice = myDeviceManager.GraphicsDevice;
                myTexture = content.Load<Texture2D>(textureAsset);
                m_rand = new Random();

                m_vDec = new VertexDeclaration(myDevice, VertexParticle.VertexElements);
                positionColorVertexDeclaration = new VertexDeclaration(myDevice, VertexPositionColor.VertexElements);

                effect = content.Load<Effect>(shaderAsset);
                try
                {
                    effect.Parameters["particleTexture"].SetValue(myTexture);
                }
                catch { }
            //reading the LiDAR File    
            RefreshParticles();
            
            base.LoadContent();
        }
        /// <summary>
        /// reads in all of the LiDAR points
        /// </summary>
        private void RefreshParticles()
        {            
            //ReadLidar rl = new ReadLidar("../../../building10000pts.xyzi",'\t');
            //ReadLidar rl = new ReadLidar("../../../blueprints.xyzi",'\t');
            //ReadLidar rl = new ReadLidar("../../../cornerhouse.xyzi", ' ');
            //ReadLidar rl = new ReadLidar("../../../truck.xyzi",' ');
            //ReadLidar rl = new ReadLidar("../../../house.xyzi",' ');
            //ReadLidar rl = new ReadLidar("../../../courthouse.xyzi",' ');
            //ReadLidar rl = new ReadLidar("../../../planter.xyzi",' ');
            //ReadLidar rl = new ReadLidar("../../../LargeData.xyzi",' ');
            //ReadLidar rl = new ReadLidar("../../../smallBlock.xyzi",' ');
            ReadLidar rl = new ReadLidar("../../../kingstfaces.xyzi",' ');//whig,700k points
            //ReadLidar rl = new ReadLidar("../../../Sensor1Strip001.xyzi",'\t');
            //ReadLidar rl = new ReadLidar("../../../streetfront.xyzi", ' '); //busy, trees,fairly large amount of face
            //ReadLidar rl = new ReadLidar("../../../streetfront2.xyzi", ' ');// good road section
            //ReadLidar rl = new ReadLidar("../../../streetfront3.xyzi", ' ');//road, 1.6m points
            //ReadLidar rl = new ReadLidar("../../../streetfront4.xyzi", ' ');

            double[] x,y,z,i;

            x = rl.NormalX;
            y = rl.NormalY;
            z = rl.NormalZ;
            i = rl.I;

            float[] fx, fy, fz, fi;
            fx = new float[x.Length]; fy = new float[x.Length]; fz = new float[x.Length]; fi = new float[x.Length];

            for (int j = 0; j < x.Length; j++)
            {
                fx[j] = System.Convert.ToSingle(x[j]);
                fy[j] = System.Convert.ToSingle(y[j]);
                fz[j] = System.Convert.ToSingle(z[j]);
                fi[j] = System.Convert.ToSingle(i[j]);
            }

            int elements = rl.elements;

            m_sprites = new VertexParticle[rl.elements];
            m_decimated = new VertexParticle[(rl.elements / 5) + 1];
            float rangeI, maxI;
            rangeI = System.Convert.ToSingle(rl.Range(i));
            maxI = System.Convert.ToSingle(rl.Max(i));
            int k = 0;
            for (int j = 0; j < elements; j++)
            {
                
                m_sprites[j] = new VertexParticle();
                //m_sprites[j].Position = new Vector3(fx[j]+10, fy[j]+10, fz[j]+5);
                m_sprites[j].Position = new Vector3(fx[j], fy[j], fz[j]);
                m_sprites[j].Color = new Color((byte)fi[j], (byte)fi[j], (byte)fi[j]);
                //m_sprites[j].Color = new Color(new Vector3((((rangeI - (maxI - fi[j])) / rangeI)*255), (((rangeI - (maxI - fi[j])) / rangeI)*255), (((rangeI - (maxI - fi[j])) / rangeI)*255)));
                m_sprites[j].Data = new Vector4(0, 0, 0, 0);
                #region decimation
                if (j % 5 == 0)
                {
                    if (k >= m_decimated.Length)
                        break;
                    m_decimated[k] = new VertexParticle();
                    m_decimated[k].Position = new Vector3(fx[j], fy[j], fz[j]);
                    m_decimated[k].Color = new Color((byte)fi[j], (byte)fi[j], (byte)fi[j]);
                    m_decimated[k].Data = new Vector4(0, 0, 0, 0);
                    k++;
                }
                #endregion
            }
            #region marchingCubes
            //mc = new MarchingCubes(x, 2);
            //faceX = mc.March();
            //mc = new MarchingCubes(y, 2);
            //faceY = mc.March();
            //mc = new MarchingCubes(z, 2);
            //faceZ = mc.March();
            #endregion

            partCount = m_sprites.Length;
                                 
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }
       
        public override void Draw(GameTime gameTime)
        {
            bool PointSpriteEnable = myDevice.RenderState.PointSpriteEnable;
            float PointSize = myDevice.RenderState.PointSize;
            bool AlphaBlendEnable = myDevice.RenderState.AlphaBlendEnable;
            BlendFunction AlphaBlendOperation = myDevice.RenderState.AlphaBlendOperation;
            Blend SourceBlend = myDevice.RenderState.SourceBlend;
            Blend DestinationBlend = myDevice.RenderState.DestinationBlend;
            bool SeparateAlphaBlendEnabled = myDevice.RenderState.SeparateAlphaBlendEnabled;
            bool AlphaTestEnable = myDevice.RenderState.AlphaTestEnable;
            CompareFunction AlphaFunction = myDevice.RenderState.AlphaFunction;
            int ReferenceAlpha = myDevice.RenderState.ReferenceAlpha;
            bool DepthBufferWriteEnable = myDevice.RenderState.DepthBufferWriteEnable;

            if (myDevice.RenderState.PointSpriteEnable != true)
                myDevice.RenderState.PointSpriteEnable = true;
            if (myDevice.RenderState.PointSize != myPointSize)
                myDevice.RenderState.PointSize = myPointSize;
            if (myDevice.RenderState.AlphaBlendEnable != true)
                myDevice.RenderState.AlphaBlendEnable = true;
            if (myDevice.RenderState.AlphaBlendOperation != BlendFunction.Add)
                myDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;
            if (myDevice.RenderState.SourceBlend != Blend.SourceAlpha)
                myDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            if (myDevice.RenderState.DestinationBlend != Blend.One)
                myDevice.RenderState.DestinationBlend = Blend.One;
            if (myDevice.RenderState.SeparateAlphaBlendEnabled != false)
                myDevice.RenderState.SeparateAlphaBlendEnabled = false;
            if (myDevice.RenderState.AlphaTestEnable != true)
                myDevice.RenderState.AlphaTestEnable = true;
            if (myDevice.RenderState.AlphaFunction != CompareFunction.Greater)
                myDevice.RenderState.AlphaFunction = CompareFunction.Greater;
            if (myDevice.RenderState.ReferenceAlpha != 0)
                myDevice.RenderState.ReferenceAlpha = 0;
            if (myDevice.RenderState.DepthBufferWriteEnable != false)
                myDevice.RenderState.DepthBufferWriteEnable = false;

            myDevice.VertexDeclaration = m_vDec;

            Matrix wvp = (Matrix.CreateScale(myScale) * Matrix.CreateFromQuaternion(myRotation) * Matrix.CreateTranslation(myPosition)) * Camera.myView * Camera.myProjection;
            effect.Parameters["WorldViewProj"].SetValue(wvp); 

            effect.Begin();

            
            //VertexParticle[] houseface = new VertexParticle[1];            
            //houseface[0].Position = new Vector3((float)faceX,0, 0);
            //houseface[0].Color = Color.GreenYellow;

            for (int ps = 0; ps < effect.CurrentTechnique.Passes.Count; ps++)
            {
                effect.CurrentTechnique.Passes[ps].Begin();

                if (Keyboard.GetState().IsKeyDown(Keys.Q) ||
                    Keyboard.GetState().IsKeyDown(Keys.W) ||
                    Keyboard.GetState().IsKeyDown(Keys.E) ||
                    Keyboard.GetState().IsKeyDown(Keys.A) ||
                    Keyboard.GetState().IsKeyDown(Keys.S) ||
                    Keyboard.GetState().IsKeyDown(Keys.D))
                    myDevice.DrawUserPrimitives<VertexParticle>(PrimitiveType.PointList, m_decimated, 0, m_decimated.Length);
                else
                    myDevice.DrawUserPrimitives<VertexParticle>(PrimitiveType.PointList, m_sprites, 0, m_sprites.Length);

                //myDevice.DrawUserPrimitives<VertexParticle>(PrimitiveType.PointList, houseface, 0, 1);
                
                effect.CurrentTechnique.Passes[ps].End();               
            }
            effect.End();

            if (myDevice.RenderState.PointSpriteEnable != PointSpriteEnable)
                myDevice.RenderState.PointSpriteEnable = PointSpriteEnable;
            if (myDevice.RenderState.PointSize != PointSize)
                myDevice.RenderState.PointSize = PointSize;
            if (myDevice.RenderState.AlphaBlendEnable != AlphaBlendEnable)
                myDevice.RenderState.AlphaBlendEnable = AlphaBlendEnable;
            if (myDevice.RenderState.AlphaBlendOperation != AlphaBlendOperation)
                myDevice.RenderState.AlphaBlendOperation = AlphaBlendOperation;
            if (myDevice.RenderState.SourceBlend != SourceBlend)
                myDevice.RenderState.SourceBlend = SourceBlend;
            if (myDevice.RenderState.DestinationBlend != DestinationBlend)
                myDevice.RenderState.DestinationBlend = DestinationBlend;
            if (myDevice.RenderState.SeparateAlphaBlendEnabled != SeparateAlphaBlendEnabled)
                myDevice.RenderState.SeparateAlphaBlendEnabled = SeparateAlphaBlendEnabled;
            if (myDevice.RenderState.AlphaTestEnable != AlphaTestEnable)
                myDevice.RenderState.AlphaTestEnable = AlphaTestEnable;
            if (myDevice.RenderState.AlphaFunction != AlphaFunction)
                myDevice.RenderState.AlphaFunction = AlphaFunction;
            if (myDevice.RenderState.ReferenceAlpha != ReferenceAlpha)
                myDevice.RenderState.ReferenceAlpha = ReferenceAlpha;
            if (myDevice.RenderState.DepthBufferWriteEnable != DepthBufferWriteEnable)
                myDevice.RenderState.DepthBufferWriteEnable = DepthBufferWriteEnable;

            base.Draw(gameTime);
        }
        public Vector3 getParticle(int i)
        {
            Vector3 v = m_sprites[i].Position;
            return v;
        }
    }

}
